package com.sf.framework.servlet;

import com.sf.framework.annotaction.*;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *  仿写 DispatchServlet
 */
public class SFDispatchServlet extends HttpServlet {
    /**
     *  加载配置文件类
     */
    private Properties properties = new Properties();
    /**
     *  class 集合
     */
    private List<String> classNameList = new ArrayList<>();
    /**
     *  ioc 容器
     */
    private Map<String,Object> ioc = new HashMap<String,Object>();
    /**
     *  requestMapping 存储
     */
    private List<HandlerMapping> requestMappingList = new ArrayList<>();
    /**
     *  临时存储
     */
    private List<String> urlList = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.读取配置文件
        try {
            initConfig(config);
            //2.扫描class 文件
            initScannerClass();
            //3.实例化
            initInstance();
            //4.依赖注入
            initAutowrite();
            //5.初始化 访问路径 - 对应执行类及方法
            initHandlerMapping();

            System.out.println("SF Framework init success!!!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *  初始化配置文件
     * @param config
     */
    private void initConfig(ServletConfig config){
        //获取要扫描的包信息
        String initParameter = config.getInitParameter("contextConfigLocation");
        // getClassLoader() 默认扫描 resources 目录下的文件 (测试中 把文件目录 写成了 resource 结果没有扫描到配置文件)
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(initParameter);
        try{
            if(resourceAsStream == null){
                throw new Exception("config file not exist!");
            }
            properties.load(resourceAsStream);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                if(resourceAsStream != null){
                    resourceAsStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *  扫描类
     */
    private void initScannerClass() {
        //获取 配置文件 中配置的 要扫描的包
        String basePackage = properties.getProperty("scanner.package");
        //把包名 转成 文件目录层次名
        URL resource = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
        // 获取文件的 全路径名
        File file = new File(resource.getFile());
        //递归方法
        handlerClass(basePackage,file);
    }

    /**
     *  递归扫描文件
     * @param file
     */
    private void handlerClass(String basePackage,File file) {
        if(file.isDirectory()){
            //获取该目录下 所有的文件
            File[] files = file.listFiles();
            for (File f : files){
                if(f.isDirectory()){
                    //如果是目录 拼接包名 并 递归
                    handlerClass(basePackage+"."+f.getName(),f);
                }else{
                    //拼接包名
                    classNameList.add(basePackage+"."+f.getName().replaceAll(".class",""));
                }
            }
        }else{
            //拼接包名
            classNameList.add(basePackage+"."+file.getName().replaceAll(".class",""));
        }
    }

    /**
     *  初始化实例 ioc 容器
     */
    private void initInstance() {
        if(classNameList.isEmpty()) return;;
        classNameList.forEach(className->{
            try {
                Class<?> aClass = Class.forName(className);
                // 判断是 Controller 方法
                if(aClass.isAnnotationPresent(SFController.class)){
                    Object newInstance = aClass.newInstance();
                    ioc.put(toLowerCase(aClass.getSimpleName()),newInstance);
                }else if(aClass.isAnnotationPresent(SFService.class)){
                    //1.自定义的
                    SFService annotation = aClass.getAnnotation(SFService.class);
                    String beanName = annotation.value();
                    //2.默认的beanName
                    if("".equals(beanName)){
                        beanName = toLowerCase(aClass.getSimpleName());
                    }
                    Object newInstance = aClass.newInstance();
                    ioc.put(beanName,newInstance);

                    // -------------------------------------------------------------------------------------------------
                    //投机行为 兼容 接口对象 注入 实现类的对象
                    Class<?>[] interfaces = aClass.getInterfaces();
                    if (interfaces.length>1) throw new Exception("this ".concat(aClass.getSimpleName()).concat(" implementation interface to many"));
                    for (Class classs : interfaces){
                        ioc.put(classs.getName(),newInstance);
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    /**
     *  首字母转小写
     *  利用 char 的特性 使用 Ascii 码值 转换字母大小写
     * @param simpleName
     * @return
     */
    private String toLowerCase(String simpleName){
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    /**
     *  实现依赖注入
     */
    private void initAutowrite() {
        if(ioc.isEmpty()) return;
        Set<Map.Entry<String, Object>> entries = ioc.entrySet();
        for (Map.Entry entry : entries){
            //获取 这个对象的所有属性
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields){
                //这个属性使用了 自动注入 注解
                boolean annotationPresent = field.isAnnotationPresent(SFAutowrite.class);
                if(annotationPresent){
                    SFAutowrite annotation = field.getAnnotation(SFAutowrite.class);
                    // 获取自定义的 beanName
                    String beanName = annotation.value();
                    if ("".equals(beanName)){
                        // 获取 投机 写入 ioc 容器的对象 通过接口 的全限定包名
                        beanName = field.getType().getName();
                    }
                    //暴力访问，对私有对象 操作
                    field.setAccessible(true);
                    try {
                        // 设置属性的值
                        //1.参数 1 是 被设置属性的 对象，参数 2 是 属性的 实例
                        field.set(entry.getValue(),ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     *  初始化 HandlerMapping
     */
    private void initHandlerMapping() throws Exception {
        if(ioc.isEmpty()) return;
        Set<Map.Entry<String, Object>> entries = ioc.entrySet();
        for (Map.Entry entry : entries){
            Class<?> aClass = entry.getValue().getClass();
            if(!aClass.isAnnotationPresent(SFController.class)) continue;
            SFRequestMapping annotation = aClass.getAnnotation(SFRequestMapping.class);
            //获取 该 controller 下所有的方法
            Method[] methods = aClass.getMethods();
            for (Method method : methods){
                if(!method.isAnnotationPresent(SFRequestMapping.class)) continue;
                // 兼容 有无 / 的情况 和 多写了 // 的情况
                String url = ("/"+annotation.value() +
                        "/"+method.getAnnotation(SFRequestMapping.class).value()).replaceAll("/+","/");
                //判断 访问路径 是否有重复的
                if(urlList.contains(url)) throw new Exception("this ".concat(url).concat("exist!"));
                urlList.add(url);
                // url 转换成 正则，方便匹配
                Pattern pattern = Pattern.compile(url);
                requestMappingList.add(new HandlerMapping(entry.getValue(),method,pattern));
                System.out.println("Mapping url:".concat(url).concat("------").concat(method.getName()));
            }
            //清空 gc 会回收
            urlList = null;
        }
    }

    /**
     *  访问路径，详细信息类
     */
    class HandlerMapping{

        private Object controller;
        private Method method;
        //正则
        private Pattern url;
        //参数顺序
        protected Map<String,Integer> paramIndexMapping;

        public Map<String, Integer> getParamIndexMapping() {
            return paramIndexMapping;
        }

        public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
            this.paramIndexMapping = paramIndexMapping;
        }

        public HandlerMapping(Object controller, Method method, Pattern url) {
            this.controller = controller;
            this.method = method;
            this.url = url;

            paramIndexMapping = new HashMap<>();

            //处理自定的参数
            //注意这里获取的是 参数注解 是一个二维数组
            Annotation[][] annotations = method.getParameterAnnotations();
            if(annotations!=null && annotations.length>0){
                for(int i=0;i<annotations.length;i++){
                    for (Annotation annotation : annotations[i]){
                        if(annotation instanceof SFRequestParam){
                            //获取参数注解内的 名命 内容
                            String value = ((SFRequestParam) annotation).value();
                            if(!"".equals(value)){
                                paramIndexMapping.put(value,i);
                            }
                        }
                    }
                }
            }

            // 方法中注入了 request 和 response 的
            //处理 request 和 response
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i=0;i<parameterTypes.length;i++){
                Class<?> type = parameterTypes[i];
                if(type == HttpServletRequest.class
                ||type == HttpServletResponse.class){
                    paramIndexMapping.put(parameterTypes[i] .getName(),i);
                }
            }

        }

        public Object getController() {
            return controller;
        }

        public void setController(Class controller) {
            this.controller = controller;
        }

        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

        public Pattern getUrl() {
            return url;
        }

        public void setUrl(Pattern url) {
            this.url = url;
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req,resp);
    }

    /**
     *  根据路径 执行对应的方法
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 通过 request 获取 对应的处理器
        HandlerMapping handler = getHandler(req);
        if (handler == null) resp.getWriter().write("404 Not Found！");

        //获取请求方法的参数类型
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();

        //保存所有需要自动赋值的参数值
        // 初始化 一个参数的容器
        Object [] paramValues = new Object[parameterTypes.length];
        // 获取 请求 request 传过来的参数
        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()){
            //获取前端 传来的参数
            String value = Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
            //如果找到匹配的参数，则开始填充参数值，
            // 匹配 目标方法的参数
            if(!handler.paramIndexMapping.containsKey(entry.getKey())){continue;}
            //获取参数 在目标方法的 索引位置
            int index = handler.paramIndexMapping.get(entry.getKey());
            //把 参数的 类型 和 值 传入 convert，类型转换
            paramValues[index] = convert(parameterTypes[index],value);

        }

        //设置方法中的request和response对象
        int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
        paramValues[reqIndex] = req;
        int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
        paramValues[respIndex] = resp;


        try {
            handler.method.invoke(handler.controller, paramValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    //url传过来的参数都是String类型的，HTTP是基于字符串协议
    //只需要把String转换为任意类型就好
    private Object convert(Class<?> type,String value){
        if(Integer.class == type){
            return Integer.valueOf(value);
        }
        //如果还有double或者其他类型，继续加if
        //这时候，我们应该想到策略模式了
        //在这里暂时不实现，希望小伙伴自己来实现，
        // TODO 暂时还没学习设计模式，后面添加
        return value;
    }

    /**
     *  传入 request 返回HandlerMapping
     * @param req
     * @return
     */
    private HandlerMapping getHandler(HttpServletRequest req) {
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        // 兼容 设置了项目访问前缀的
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        for(HandlerMapping handlerMapping : requestMappingList){
            Pattern pattern = handlerMapping.getUrl();
            Matcher matcher = pattern.matcher(url);
            if(matcher.matches()){
               return handlerMapping;
            }
        }
        return null;
    }
}
